libobs_wrapper\scenes\scene_item/
mod.rs

1//! Scene items essentially hold the transform information and the source in a scene itself.
2//! They are specific to the scene which they were created in.
3mod traits;
4pub use traits::SceneItemExtSceneTrait;
5
6use std::{fmt::Debug, hash::Hash, sync::Arc};
7
8use libobs::{obs_scene_item, obs_transform_info, obs_video_info};
9
10use crate::{
11    enums::ObsBoundsType,
12    graphics::Vec2,
13    impl_obs_drop,
14    macros::trait_with_optional_send_sync,
15    run_with_obs,
16    runtime::ObsRuntime,
17    scenes::{ObsSceneRef, ObsTransformInfo, ObsTransformInfoBuilder},
18    sources::ObsSourceTrait,
19    unsafe_send::{Sendable, SmartPointerSendable},
20    utils::{ObsDropGuard, ObsError},
21};
22
23#[derive(Debug)]
24pub(super) struct _ObsSceneItemDropGuard {
25    scene_item: Sendable<*mut obs_scene_item>,
26    runtime: ObsRuntime,
27}
28
29impl ObsDropGuard for _ObsSceneItemDropGuard {}
30impl_obs_drop!(_ObsSceneItemDropGuard, (scene_item), move || unsafe {
31    // Safety: The pointer is valid as long as we are in the runtime and the guard is alive.
32    // Because scene item is attached to a scene, we first remove it from the scene and then release it.
33    libobs::obs_sceneitem_remove(scene_item.0);
34    // Release is called under the hood
35});
36
37#[derive(Debug, Clone)]
38/// Holds the specific source that was added to the scene and its scene item.
39/// If this struct is attached to the scene, it'll not be dropped as the scene
40/// internally stores this struct, thus the source will also not be dropped.
41pub struct ObsSceneItemRef<T: ObsSourceTrait + Clone> {
42    // Drop the scene item first...
43    scene_item_ptr: SmartPointerSendable<*mut obs_scene_item>,
44    runtime: ObsRuntime,
45    // Then the scene
46    // Note: Ideally, we'd want to keep the whole ObsScene struct, however
47    // that would lead to a circular dependency, meaning that this SceneItem / the scene
48    // would never be dropped. Because the only argument the scene takes is its name
49    // and there are no settings attached to it, it's safe to only have a SmartPointer
50    // and not the full scene
51    _scene_ptr: SmartPointerSendable<*mut libobs::obs_scene>,
52
53    // And at last the source
54    underlying_source: T,
55}
56
57impl<T: ObsSourceTrait + Clone> ObsSceneItemRef<T> {
58    pub(crate) fn new(
59        scene: &ObsSceneRef,
60        source: T,
61        runtime: ObsRuntime,
62    ) -> Result<Self, ObsError> {
63        let scene_ptr = scene.as_ptr();
64        let source_ptr = source.as_ptr();
65
66        let scene_item_ptr = run_with_obs!(runtime, (scene_ptr, source_ptr), move || {
67            let ptr = unsafe {
68                // Safety: The pointers are valid as they are safe pointers
69                libobs::obs_scene_add(scene_ptr.get_ptr(), source_ptr.get_ptr())
70            };
71
72            if ptr.is_null() {
73                Err(ObsError::NullPointer(None))
74            } else {
75                Ok(Sendable(ptr))
76            }
77        })??;
78
79        let drop_guard = _ObsSceneItemDropGuard {
80            scene_item: scene_item_ptr.clone(),
81            runtime: runtime.clone(),
82        };
83
84        let scene_item_ptr = SmartPointerSendable::new(scene_item_ptr.0, Arc::new(drop_guard));
85
86        Ok(Self {
87            underlying_source: source,
88            _scene_ptr: scene.as_ptr().clone(),
89            scene_item_ptr,
90            runtime,
91        })
92    }
93}
94
95trait_with_optional_send_sync! {
96    pub trait SceneItemTrait: Debug {
97        fn as_ptr(&self) -> &SmartPointerSendable<*mut obs_scene_item>;
98        fn runtime(&self) -> ObsRuntime;
99        fn inner_source_dyn(&self) -> &dyn ObsSourceTrait;
100        fn inner_source_dyn_mut(&mut self) -> &mut dyn ObsSourceTrait;
101
102        /// Gets the transform info of the given source in this scene.
103        fn get_transform_info(&self) -> Result<ObsTransformInfo, ObsError> {
104            let self_ptr = self.as_ptr();
105            let item_info = run_with_obs!(self.runtime(), (self_ptr), move || {
106                let mut item_info: obs_transform_info = unsafe {
107                    // Safety: this is safe to call because we are filling a struct with zeros
108                    std::mem::zeroed()
109                };
110                unsafe {
111                    // Safety: Fill the transform info struct with the data
112                    libobs::obs_sceneitem_get_info2(self_ptr.get_ptr(), &mut item_info)
113                };
114
115                ObsTransformInfo(item_info)
116            })?;
117
118            Ok(item_info)
119        }
120
121        /// Gets the position of the given source in this scene.
122        fn get_source_position(&self) -> Result<Vec2, ObsError> {
123            let self_ptr = self.as_ptr();
124            let position = run_with_obs!(self.runtime(), (self_ptr), move || {
125                let main_pos = unsafe {
126                    // Safety: this is safe to call because we a filling a struct with zeros
127                    let mut main_pos: libobs::vec2 = std::mem::zeroed();
128
129                    // Safety: Fill the vec2 struct with the position data
130                    libobs::obs_sceneitem_get_pos(self_ptr.get_ptr(), &mut main_pos);
131
132                    main_pos
133                };
134
135                Vec2::from(main_pos)
136            })?;
137
138            Ok(position)
139        }
140
141        /// Gets the scale of the given source in this scene.
142        fn get_source_scale(&self) -> Result<Vec2, ObsError> {
143            let self_ptr = self.as_ptr();
144            let scale = run_with_obs!(self.runtime(), (self_ptr), move || {
145                let main_pos = unsafe {
146                    // Safety: this is safe to call because we a filling a struct with zeros
147                    let mut main_pos: libobs::vec2 = std::mem::zeroed();
148
149                    // Safety: Fill the vec2 struct with the scale data, this using the correct size
150                    libobs::obs_sceneitem_get_scale(self_ptr.get_ptr(), &mut main_pos);
151
152                    main_pos
153                };
154
155                Vec2::from(main_pos)
156            })?;
157
158            Ok(scale)
159        }
160
161        /// Sets the position of the given source in this scene.
162        fn set_source_position(&self, position: Vec2) -> Result<(), ObsError> {
163            let self_ptr = self.as_ptr();
164
165            run_with_obs!(self.runtime(), (self_ptr), move || {
166                let position: libobs::vec2 = position.into();
167
168                unsafe {
169                    // Safety: The pointer is valid as it is a safe pointer
170                    libobs::obs_sceneitem_set_pos(self_ptr.get_ptr(), &position);
171                }
172            })?;
173
174            Ok(())
175        }
176
177        /// Sets the transform info of the given source in this scene.
178        /// The `ObsTransformInfo` can be built by using the `ObsTransformInfoBuilder`.
179        fn set_transform_info(&self, info: &ObsTransformInfo) -> Result<(), ObsError> {
180            let item_info = Sendable(info.clone());
181            let self_ptr = self.as_ptr();
182
183            run_with_obs!(self.runtime(), (self_ptr, item_info), move || {
184                let item_info = item_info.0 .0;
185
186                unsafe {
187                    // Safety: The pointers are valid as they are safe pointers
188                    libobs::obs_sceneitem_set_info2(self_ptr.get_ptr(), &item_info);
189                }
190            })?;
191
192            Ok(())
193        }
194
195        /// Fits the given source to the screen size.
196        /// If the source is locked, no action is taken.
197        ///
198        /// Returns `Ok(true)` if the source was resized, `Ok(false)` if the source was locked and not resized.
199        fn fit_source_to_screen(&self) -> Result<bool, ObsError> {
200            let self_ptr = self.as_ptr();
201            let is_locked = {
202                run_with_obs!(self.runtime(), (self_ptr), move || unsafe {
203                    // Safety: The pointer is valid as it is a safe pointer
204                    libobs::obs_sceneitem_locked(self_ptr.get_ptr())
205                })?
206            };
207
208            if is_locked {
209                return Ok(false);
210            }
211
212            let ovi = run_with_obs!(self.runtime(), (), move || {
213                let mut ovi = std::mem::MaybeUninit::<obs_video_info>::uninit();
214                let success = unsafe {
215                    // Safety: This is safe because we are providing a valid pointer to be filled
216                    libobs::obs_get_video_info(ovi.as_mut_ptr())
217                };
218
219                if success {
220                    let res = unsafe {
221                        // Safety: This is safe because libobs filled the pointer and returned success
222                        ovi.assume_init()
223                    };
224
225                    Ok(Sendable(res))
226                } else {
227                    Err(ObsError::NullPointer(Some(
228                        "Failed to get video info".to_string(),
229                    )))
230                }
231            })??;
232
233            let bounds_crop = run_with_obs!(self.runtime(), (self_ptr), move || {
234                unsafe {
235                    // Safety: The pointer is valid as it is a safe pointer
236                    libobs::obs_sceneitem_get_bounds_crop(self_ptr.get_ptr())
237                }
238            })?;
239
240            // We are not constructing it from the source here because we want to reset full transform (so we use build instead of build_with_fallback)
241            let item_info = ObsTransformInfoBuilder::new()
242                .set_bounds_type(ObsBoundsType::ScaleInner)
243                .set_crop_to_bounds(bounds_crop)
244                .build(ovi.0.base_width, ovi.0.base_height);
245
246            self.set_transform_info(&item_info)?;
247            Ok(true)
248        }
249
250        /// Sets the scale of the given source in this scene.
251        fn set_source_scale(&self, scale: Vec2) -> Result<(), ObsError> {
252            let self_ptr = self.as_ptr();
253
254            run_with_obs!(self.runtime(), (self_ptr), move || {
255                let scale: libobs::vec2 = scale.into();
256
257                unsafe {
258                    // Safety: The pointer is valid as it is a safe pointer
259                    libobs::obs_sceneitem_set_scale(self_ptr.get_ptr(), &scale);
260                }
261            })?;
262
263            Ok(())
264        }
265    }
266}
267
268impl<T: ObsSourceTrait + Clone> SceneItemTrait for ObsSceneItemRef<T> {
269    fn as_ptr(&self) -> &SmartPointerSendable<*mut obs_scene_item> {
270        &self.scene_item_ptr
271    }
272
273    fn runtime(&self) -> ObsRuntime {
274        self.runtime.clone()
275    }
276
277    fn inner_source_dyn(&self) -> &dyn ObsSourceTrait {
278        &self.underlying_source
279    }
280
281    fn inner_source_dyn_mut(&mut self) -> &mut dyn ObsSourceTrait {
282        &mut self.underlying_source
283    }
284}
285
286impl<T> ObsSceneItemRef<T>
287where
288    T: ObsSourceTrait + Clone,
289{
290    /// Returns a reference to the specific source type.
291    pub fn inner_source(&self) -> &T {
292        &self.underlying_source
293    }
294
295    /// Returns a reference to the specific source type.
296    pub fn inner_source_mut(&mut self) -> &mut T {
297        &mut self.underlying_source
298    }
299}
300
301// The macro doesn't support generics yet, so we implement it manually
302//impl_eq_of_ptr!(SceneItemRef<T>, scene_item_ptr);
303
304impl<T: ObsSourceTrait + Clone> PartialEq for ObsSceneItemRef<T> {
305    fn eq(&self, other: &Self) -> bool {
306        self.scene_item_ptr.get_ptr() == other.scene_item_ptr.get_ptr()
307    }
308}
309
310impl<T: ObsSourceTrait + Clone> Eq for ObsSceneItemRef<T> {}
311
312impl<T: ObsSourceTrait + Clone> Hash for ObsSceneItemRef<T> {
313    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
314        self.scene_item_ptr.get_ptr().hash(state);
315    }
316}